/**********************************************************************
 * file:  sr_router.c 
 * date:  Mon Feb 18 12:50:42 PST 2002  
 * Contact: casado@stanford.edu 
 *
 * Description:
 * 
 * This file contains all the functions that interact directly
 * with the routing table, as well as the main entry method
 * for routing.
 *
 **********************************************************************/

#include <stdio.h>
#include <assert.h>

#include "sr_if.h"
#include "sr_rt.h"
#include "sr_router.h"

#ifndef _SR_PROTOCOL_H
#define _SR_PROTOCOL_H
#include "sr_protocol.h"
#endif

#include <string.h>
#include <stdlib.h>
#include <time.h>

// DEBUG print for IP address
#define IPCOMP(addr, n) ((addr >> (24 - 8 * n)) & 0xFF)

void cache_arp(struct sr_instance* sr, struct sr_arphdr *arp_packet);
void check_arp_cache(struct sr_instance* sr);

/*--------------------------------------------------------------------- 
 * Method: sr_init(void)
 * Scope:  Global
 *
 * Initialize the routing subsystem
 * 
 *---------------------------------------------------------------------*/

void sr_init(struct sr_instance* sr) 
{
    /* REQUIRES */
    assert(sr);

    /* Add initialization code here! */

} /* -- sr_init -- */



/*---------------------------------------------------------------------
 * Method: sr_handlepacket(uint8_t* p,char* interface)
 * Scope:  Global
 *
 * This method is called each time the router receives a packet on the
 * interface.  The packet buffer, the packet length and the receiving
 * interface are passed in as parameters. The packet is complete with
 * ethernet headers.
 *
 * Note: Both the packet buffer and the character's memory are handled
 * by sr_vns_comm.c that means do NOT delete either.  Make a copy of the
 * packet instead if you intend to keep it around beyond the scope of
 * the method call.
 *
 *---------------------------------------------------------------------*/

void sr_handlepacket(struct sr_instance* sr, 
        uint8_t * packet/* lent */,
        unsigned int len,
        char* interface/* lent */)
{
    /* REQUIRES */
    assert(sr);
    assert(packet);
    assert(interface);

    ////////////////////////*Naghmeh*////////////////////////
    uint8_t* packetCopy = malloc(sizeof(uint8_t) * len);
    memcpy(packetCopy, packet, len);

	//DEBUG
	hex_dump(packetCopy, len); 
    
	struct sr_ethernet_hdr* ethernetHeader = (struct sr_ethernet_hdr*) packetCopy;
	struct sr_if* myInterface = sr_get_interface(sr, interface);

    if(ntohs(ethernetHeader->ether_type) == ETHERTYPE_ARP)
    {
		printf("Received an ARP packet\n");
		struct sr_arphdr* arpHeader = (struct sr_arphdr *) (packetCopy + sizeof(struct sr_ethernet_hdr));
		if(ntohs(arpHeader->ar_op) == ARP_REQUEST && arpHeader->ar_tip == myInterface->ip)
		{
			printf("Received an ARP request\n");
			arpHeader->ar_op = htons(ARP_REPLY);
			memcpy(arpHeader->ar_tha, arpHeader->ar_sha, ETHER_ADDR_LEN);
			arpHeader->ar_tip = arpHeader->ar_sip;
			memcpy(arpHeader->ar_sha, myInterface->addr, ETHER_ADDR_LEN);
			arpHeader->ar_sip = myInterface->ip;

			memcpy(ethernetHeader->ether_dhost, arpHeader->ar_tha, ETHER_ADDR_LEN);
			memcpy(ethernetHeader->ether_shost, arpHeader->ar_sha, ETHER_ADDR_LEN);

			if (sr_send_packet(sr, packetCopy, len, interface) != 0){
				printf("Failed to send ARP response!\n");
			}

		}
		else if (ntohs(arpHeader->ar_op) == ARP_REPLY)
		{
			printf("Received an ARP response.\n");
	//		cache_arp(sr, arpHeader);
		}
    }
    //check_arp_cache(sr);
    /////////////////////////////////////////////////////////
    /* KC */
    else if (ntohs(ethernetHeader->ether_type) == ETHERTYPE_IP)
    {
		struct sr_if* nextHopInt;
			
		uint8_t  ipHdrLen        = 0;
		uint8_t  ipLen           = 0;
		uint16_t receivedCsum    = 0;	
		printf("DEBUG: Received an IP packet\n");
		struct ip* ipPacket      = (struct ip *)(packetCopy + sizeof(struct sr_ethernet_hdr));
		ipHdrLen                 = (ipPacket->ip_hl) << 2;
		ipLen                    = ntohs(ipPacket->ip_len);

		printf("DEBUG: Source IP: %x and Dest IP: %x Requested interface %s's IP:%x\n",ntohl(ipPacket->ip_src.s_addr), ntohl(ipPacket->ip_dst.s_addr), myInterface->name, ntohl(myInterface->ip));

		// Check if packet received is an ICMP ping request.
	    if((ntohl(ipPacket->ip_dst.s_addr) == ntohl(myInterface->ip)) && ipPacket->ip_p == IPPROTO_ICMP)
		{
			struct icmp* icmpPacket;
			/* Before calculating ICMP checksum, the checksum field must be set to 0 */
		// TODO: Implement ICMP ping request processing functionality.

			icmpPacket           = (struct icmp*)(packetCopy + sizeof(struct sr_ethernet_hdr) + sizeof(struct ip));
			receivedCsum         = ntohs(icmpPacket->checksum);
			printf("DEBUG: ICMP type = %x ICMP code = %x Checksum=%x\n",icmpPacket->type,icmpPacket->code,ntohs(icmpPacket->checksum));
			icmpPacket->checksum = 0;
			if(receivedCsum == htons(checkSum(icmpPacket, (ntohs(ipPacket->ip_len) - (ipPacket->ip_hl * sizeof(uint32_t))))))
				printf("DEBUG: Hurrah!!!!\n");

		}
		// Store and verify if received IP packet has correct checksum. Note that the ip_sum field in the IP packet must be reset before checksum calculation.
		receivedCsum             = ipPacket->ip_sum;
		ipPacket->ip_sum         = 0;

		if(receivedCsum == checkSum(ipPacket, sizeof(struct ip)))
		{
			printf("DEBUG: IP Checksum Hurrah!!\n");
			// Decrement TTL and recalculate IP checksum
			ipPacket->ip_ttl--;
			ipPacket->ip_sum     = checkSum(ipPacket,sizeof(struct ip));
			
			// Determine next hop for packet.
			sr_print_routing_entry(sr->routing_table);
			sr_print_routing_table(sr);
			nextHopInt = sr_get_interface(sr, (sr->routing_table->next)->interface);
			printf("DEBUG: Next interface IP: %x\n Name:%s\n",ntohl(nextHopInt->ip),nextHopInt->name);
			
			/*ipPacket->ip_src.s_addr    = ipPacket->ip_dst.s_addr;
			ipPacket->ip_dst.s_addr    = ntohl(nextHopInt->ip);
			
			
			printf("DEBUG: Source IP: %x and Dest IP: %x Requested interface %s's IP:%x\n",ntohl(ipPacket->ip_src.s_addr), ntohl(ipPacket->ip_dst.s_addr), nextHopInt->name, ntohl(nextHopInt->ip));
			
			if (sr_send_packet(sr, packetCopy, len, nextHopInt->name) != 0)
			{
				perror("Failed to forward IP packet");
			}*/

			//TODO: Forward packet? 
			//TODO: Send ARP request to the next hop on the return path.
			//TODO: Send IP packet to the next hop on the return path.
		}


    }

    printf("*** -> Received ethernet packet of length %d \n",len);
    free(packetCopy);

}/* end sr_ForwardPacket */


/*--------------------------------------------------------------------- 
 * Method:
 *
 *---------------------------------------------------------------------*/

void cache_arp(struct sr_instance* sr, struct sr_arphdr* arpPacket){
	int i;
	int updated = -1;
	int empty_location = -1;
	for(i = 0; i < SIZE_OF_ARP_CACHE; i++)
	{
		if(arpPacket->ar_sip == sr->arp_cache[i].ip)
		{
			memcpy(sr->arp_cache[i].ha, arpPacket->ar_sha, ETHER_ADDR_LEN);
			sr->arp_cache[i].valid = 1;
			updated = 1;
		}

		if(sr->arp_cache[i].valid != 1)
		{
			empty_location = i;
		}
	}
	
	if(updated == -1)
	{
		if(empty_location == -1)
		{
			srand(time(0));
			empty_location = rand() % SIZE_OF_ARP_CACHE;
		}
		
		sr->arp_cache[empty_location].ip = arpPacket->ar_sip;
		memcpy(sr->arp_cache[empty_location].ha, arpPacket->ar_sha, ETHER_ADDR_LEN);
	}
}

void check_arp_cache(struct sr_instance* sr)
{
    int i;
    for(i =0; i<SIZE_OF_ARP_CACHE; i++)
    {
	//if(sr->arp_cache[i].valid == 1)
	//{
		printf("cached ip address %d in location %d\n", sr->arp_cache[i].ip, i)	;
	//}
    }
}

/* Function:    checkSum
 * Description: Calculate checksum value of IP and ICMP headers. In both IP and ICMP 
 *              the value of the checkSum field is initialized to 0 before calculation.
 * Parameters: 
 *          buff: This field contains all the fields of the header to be included in 
 *                the checkSum (grouped in 16 bit words).
 *          len : Length of buff in bytes.
 * Return value:
 *          checkSum: Calculated checksum for values in buff.
 * Reference: RFC 1071
 */

uint16_t checkSum(void *buff, int len)
{
    int cSum = 0;
    uint16_t* buffWd = (uint16_t *)buff;
    while(len > 1)
    {
	cSum += *buffWd++;
	len -= 2;
    }
    /* Add any left over bytes */
    if (len > 0 )
	cSum += *buffWd;
    while(cSum >> 16)
    cSum = (cSum >> 16) + (cSum & 0xFFFF);
//    cSum += (cSum >> 16);
    return ~cSum;
}

// debug function
void hex_dump(const void* in_buff,int len) {
    if (len <= 0) return;
    int i=0,j=0,k=0;
    unsigned char* buff = (unsigned char*) in_buff;
    if(!buff) return;
    printf("\n*************************** Hex Dump ***************************\n");
    for(;i<len;i++){
        // Print Hex Stuff
        printf("%02X",buff[i]);
    
        // Print ascii at the end of the line
        // Line length is 16 Hex and 16 Characters
        if(i==len-1){
            // The End of the last Hex line
            for(k=0;k<16-(i%16);k++) printf("   ");
            for(;j<=i;j++){
                // Only print printable ascii characters
                if((buff[j]>=32)&&(buff[j]<=126)) printf("%c",buff[j]);
                else printf(".");
            }   
        }else if(i%16 == 15){
            // The End of a normal Hex line
            printf("   ");
            for(;j<=i;j++){
                // Only print printable ascii characters
                if((buff[j]>=32)&&(buff[j]<=126)) printf("%c",buff[j]);
                else printf(".");
            }   
            j=i+1;  
            printf("\n");
        }else{
            // Just between Hex characters
            printf(".");
        }   
    }   
    printf("\n*******************************************************************\n");
}

